﻿using System;
using System.Collections.Generic;

namespace EnterpriseLibraryExtensions.Services.Mapping
{
    /// <summary>
    /// This abstract base class makes it easy and simple to implement mappings between two simple types - e.g.
    /// similar values in DTO objects and application logic.
    /// </summary>
    /// <typeparam name="TOneType">One of the types supported by the mapper.</typeparam>
    /// <typeparam name="TAnotherType">The other type supported by the mapper.</typeparam>
    public abstract class SimpleMapper<TOneType, TAnotherType> : EntityMapperTranslator<TOneType, TAnotherType>
    {
        private readonly Dictionary<TOneType, TAnotherType> _mappingFromOneType;
        private readonly Dictionary<TAnotherType, TOneType> _mappingFromAnotherType;

        private TOneType _fallbackOneType;
        private TAnotherType _fallbackAnotherType;
        private bool _fallbackValuesSet;

        /// <summary>
        /// Creates a new instance of the class.
        /// </summary>
        protected SimpleMapper()
        {
            _mappingFromOneType = new Dictionary<TOneType, TAnotherType>();
            _mappingFromAnotherType = new Dictionary<TAnotherType, TOneType>();

            _fallbackOneType = default(TOneType);
            _fallbackAnotherType = default(TAnotherType);
        }

        /// <summary>
        /// Add a mapping between the two types specified.
        /// </summary>
        /// <param name="oneType">The one value of the mapping.</param>
        /// <param name="anotherType">The other value of the mapping.</param>
        protected void AddMapping(TOneType oneType, TAnotherType anotherType)
        {
            _mappingFromOneType.Add(oneType, anotherType);
            _mappingFromAnotherType.Add(anotherType, oneType);
        }

        /// <summary>
        /// The value to use for either mappings of the counterpart is unknown.
        /// </summary>
        /// <param name="oneType">Default value of oneType.</param>
        /// <param name="anotherType">Default value of anotherType.</param>
        protected void SetFallbackValue(TOneType oneType, TAnotherType anotherType)
        {
            _fallbackOneType = oneType;
            _fallbackAnotherType = anotherType;
            _fallbackValuesSet = true;
        }

        /// <summary>
        /// Translates a value from TOneType to TAnotherType using the registered mappings.
        /// </summary>
        /// <param name="service">The implementaiton if <see cref="IEntityTranslatorService"/> which the translator is registered to.</param>
        /// <param name="value">The value to translate from.</param>
        /// <returns>A new instance of the translated type.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown if mapping has not been specified and no fallback value has been specifed.</exception>
        protected override TAnotherType ToAnotherType(IEntityTranslatorService service, TOneType value)
        {
            if (_mappingFromOneType.ContainsKey(value))
                return _mappingFromOneType[value];

            if (_fallbackValuesSet)
                return _fallbackAnotherType;

            throw new InvalidOperationException("Mapping not available between " + value + " of type " + typeof(TOneType) + " and type " + typeof(TAnotherType));
        }

        /// <summary>
        /// Translates a value from TAnotherType to TOneType using the registered mappings.
        /// </summary>
        /// <param name="service">The implementaiton if <see cref="IEntityTranslatorService"/> which the translator is registered to.</param>
        /// <param name="value">The value to translate from.</param>
        /// <returns>A new instance for the translated type.</returns>
        /// <exception cref="System.InvalidOperationException">Thrown if maping has not been specified and no fallback value has been set.</exception>
        protected override TOneType ToOneType(IEntityTranslatorService service, TAnotherType value)
        {
            if (_mappingFromAnotherType.ContainsKey(value))
                return _mappingFromAnotherType[value];

            if (_fallbackValuesSet)
                return _fallbackOneType;

            throw new InvalidOperationException("Mapping not available between " + value + " of type " + typeof(TAnotherType) + " and type " + typeof(TOneType));
        }
    }
}
